package info.niwota.webshot;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

public class SensorUtil {

	private static final String TAG = "SensorUtil";

	public interface SensorActionListener {
		public int LEFT = 1;
		public int RIGHT = 2;
		public int UP = 3;
		public int DOWN = 4;

		public boolean onTilt(SensorEvent event, int direction);

		public boolean onFlip(SensorEvent event);

		public boolean onLongFlip(SensorEvent event);

		public boolean onShake(SensorEvent event, float speed);

		public boolean onFaceUp(SensorEvent event);

		public boolean onFaceDown(SensorEvent event);
	}

	public static class SensorAdapter implements SensorActionListener {

		public boolean onTilt(SensorEvent event, int direction) {
			return false;
		}

		public boolean onFlip(SensorEvent event) {
			return false;
		}

		public boolean onLongFlip(SensorEvent event) {
			return false;
		}

		public boolean onShake(SensorEvent event, float speed) {
			return false;
		}

		public boolean onFaceUp(SensorEvent event) {
			return false;
		}

		public boolean onFaceDown(SensorEvent event) {
			return false;
		}
	}

	private static SensorManager sm = null;

	private static SensorEventListener listener = null;

	private static Sensor accelerometer;

	private static Sensor orientator;

	private static final int TIME_THRESHOLD = 100;

	private static final int FLIP_HOLD_THRESHOLD = 800;

	private static final int SPEED_THRESHOLD = 800; // 350

	private static final int SHAKE_TIMEOUT = 500;
	private static final int SHAKE_DURATION = 1000;
	private static final int SHAKE_COUNT = 3;

	static class SensorListener implements SensorEventListener {

		private float lastX = -0.0f;

		private float lastY = -0.0f;

		private float lastZ = -0.0f;

		private long lastTime;

		private int shakeCount = 0;

		private long lastShake;

		private long lastShakeUpdate;

		private SensorActionListener sal;

		private float lastRoll;

		private float lastPitch;

		private long lastFaceUpUpdate;

		private boolean faceUp = true;

		private boolean faceDown;

		private boolean flipped;

		public SensorListener(SensorActionListener sal) {
			this.sal = sal;
		}

		public void onAccuracyChanged(Sensor sensor, int accuracy) {
		}

		public void onSensorChanged(SensorEvent event) {
			final long now = System.currentTimeMillis();
			final long diff = (now - lastTime);

			final float[] values = event.values;

			final float x = values[SensorManager.DATA_X];
			final float y = values[SensorManager.DATA_Y];
			final float z = values[SensorManager.DATA_Z];

			final int type = event.sensor.getType();

			switch (type) {
			case Sensor.TYPE_ACCELEROMETER:
				detectFlip(now, diff, x, y, z, event);
				detectShake(now, diff, x, y, z, event);
				break;
			case Sensor.TYPE_ORIENTATION:
				detectTilt(now, diff, x, y, z, event);
				break;
			}
		}

		private void detectTilt(long now, long diff, float azimuth,
				float pitch, float roll, SensorEvent event) {

			final float resetThreshold = 3.0f;

			if (pitch > -resetThreshold && pitch < resetThreshold) {
				lastPitch = pitch;
				return;
			}

			if (roll > -resetThreshold && roll < resetThreshold) {
				lastRoll = roll;
				return;
			}

			if (diff < TIME_THRESHOLD) {
				return;
			}
			
			if (Debug.DEBUG) {
				Log.d(TAG, " @@@@@@@@@@@@@@@ detectTilt " + azimuth+":"+pitch+":"+roll);
			}
			
			final float threshold = 25.0f;

			if (pitch - lastPitch > threshold) {
				sal.onTilt(event, SensorActionListener.DOWN);
				return;
			}

			if (pitch - lastPitch < -threshold) {
				sal.onTilt(event, SensorActionListener.UP);
				return;
			}

			if (roll - lastRoll > threshold) {
				sal.onTilt(event, SensorActionListener.LEFT);
				return;
			}

			if (roll - lastRoll < -threshold) {
				sal.onTilt(event, SensorActionListener.RIGHT);
				return;
			}
			
			
			lastTime = now;
		}

		private void detectFlip(long now, long diff, float x, float y, float z,
				SensorEvent event) {

			if (diff < TIME_THRESHOLD) {
				return;
			}

			
			if (Debug.DEBUG) {
				Log.d(TAG, "*** detectFlip  elapse: " + diff + " lastTime: "
						+ lastTime + " now: " + now + "  x: " + x + " y: " + y
						+ " z: " + z);
			}
			
			// flip
			if (z >= 0) {
				sal.onFaceUp(event);
				
				if (flipped) {
					sal.onFlip(event);
					flipped = false;
				}
				
				//
				faceUp = true;
				faceDown = false;
				lastFaceUpUpdate = now;
			} else if (z < 0) {
				sal.onFaceDown(event);
				
				flipped = flipped || faceUp;
				
				if (flipped && now - lastFaceUpUpdate > FLIP_HOLD_THRESHOLD) {
					sal.onLongFlip(event);
					flipped = false;
				}
				
				//
				faceDown = true;
				faceUp = false;
			}
			
			
			lastTime = now;
		}

		private void detectShake(long now, long diff, float x, float y,
				float z, SensorEvent event) {

			if (diff < TIME_THRESHOLD) {
				return;
			}
			
			if (Debug.DEBUG) {
				Log.d(TAG, "*** detectShake  elapse: " + diff + " lastTime: "
						+ lastTime + " now: " + now + "  x: " + x + " y: " + y
						+ " z: " + z);
			}
			
			if ((now - lastShakeUpdate) > SHAKE_TIMEOUT) {
				shakeCount = 0;
			}

			final float speed = Math.abs(x + y + z - lastX - lastY - lastZ)
					/ diff * 10000;
			//
			if (speed > SPEED_THRESHOLD) {
				if ((++shakeCount >= SHAKE_COUNT)
						&& (now - lastShake > SHAKE_DURATION)) {
					lastShake = now;
					shakeCount = 0;
					//
					sal.onShake(event, speed);
				}

				lastShakeUpdate = now;
			}

			//
			lastX = x;
			lastY = y;
			lastZ = z;
			
			lastTime = now;
		}
	}

	public static void initSensor(final Activity ctx,
			final SensorActionListener sal) {
		if (sm == null) {
			sm = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
		}

		listener = new SensorListener(sal);
		accelerometer = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		orientator = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
		
		if (Debug.DEBUG) {
			Log.d(TAG, "accelerometer: " +accelerometer);
			Log.d(TAG, "orientator: " + orientator);
		}
	}

	public static void startSensor() {
		if (accelerometer != null) {
			try {
				sm.registerListener(listener, accelerometer,
						SensorManager.SENSOR_DELAY_GAME);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		if (orientator != null) {
			try {
				sm.registerListener(listener, orientator,
						SensorManager.SENSOR_DELAY_GAME);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public static void stopSensor() {
		if (accelerometer != null) {
			try {
				sm.unregisterListener(listener, accelerometer);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		if (orientator != null) {
			try {
				sm.unregisterListener(listener, orientator);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
